#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"

// 编写一个Select服务器，使用Select来维护对应发起链接的客户端FD，其中为了维护对应FD的增长与减少，还需要维护一个数组

enum
{
	SOCK_ERR = 1,
	BIND_ERR,
	LISTEN_ERR,
};

static const uint16_t default_port = 8080;
static const int fdnum = sizeof(fd_set) * 8;
static const int defaultfd = -1;



namespace Selectns
{
	class SelectServer
	{
	public:
		static const int gbacklog = 36;

		SelectServer(uint16_t port = default_port)
			: _port(port)
		{
		}

		void Print()
        {
            std::cout << "fd list: ";
            for(int i = 0;i < fdnum;i++) 
            {
                if(_fdarr[i] != defaultfd) std::cout << _fdarr[i] << " ";
            }
            std::cout << std::endl;
        }


		void HandlerEvent(fd_set& rfds)
		{
			// 建立链接，更新rfds

			if(FD_ISSET(_listensock,&rfds))
			{
				struct sockaddr_in peer;
				socklen_t len  = sizeof(peer);

				int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
				// 如果拉客失败，那么需要终止进程吗？肯定不需要，因为可以马上寻找下一个目标。
				if (sock < 0)
				{
				    logMessage(ERROR, "accept error, quit!");
					return;
				}
				logMessage(NORMAL, "accept success!\n");

				// 将新sock存放入数组中
				for(int i = 0 ; i < fdnum ; ++i)
				{
					if(_fdarr[i]  == defaultfd)
					{
						_fdarr[i] = sock;
						break;
					}
					else if(i == fdnum)
					{
				    	logMessage(ERROR, "is full can't accept!");
						return;
					}
					else continue;
				}

				Print();
			}
		}


		// 在转去维护数组前，先将对应的TCP服务器初始化搭建好。
		void InitServer()
		{

			// 创建lisentSocket
			_listensock = socket(AF_INET, SOCK_STREAM, 0);
			if (_listensock == -1)
			{
				logMessage(FATAL, "socket build failed!");
				exit(SOCK_ERR);
			}
			logMessage(NORMAL, "Socket success");

			// 创建完毕后，bind对应端口
			struct sockaddr_in local;
			bzero(&local, sizeof(local));

			local.sin_family = AF_INET;
			local.sin_port = htons(_port);
			local.sin_addr.s_addr = htonl(INADDR_ANY);

			int n = bind(_listensock, (sockaddr *)&local, sizeof(local));
			if (n == -1)
			{
				logMessage(FATAL, "bind failed!");
				exit(BIND_ERR);
			}
			logMessage(NORMAL, "bind success");

			if (listen(_listensock, gbacklog) < 0)
			{
				logMessage(ERROR, "enter listen failed!");
				exit(LISTEN_ERR);
			}
			logMessage(NORMAL, "now sever is listening...");


			// 创建数组
			_fdarr = new int[fdnum];
			// 整个数组内部做初始化，全为-1标记为尚未有描述符
			for(int i = 0 ; i < fdnum ; ++i) _fdarr[i] = defaultfd;
			// listenscok填入数组头
			_fdarr[0] = _listensock;

		}

		void start()
		{

			while (1)
			{
				fd_set rfds;
				FD_ZERO(&rfds);
				int maxfd = _fdarr[0];

				// 如果是合法的文件描述符，添加进表
				for(int i = 0; i < fdnum ; ++i)
				{
					if(_fdarr[i]  == defaultfd) continue;
					else FD_SET(_fdarr[i],&rfds);

					// 为了select的第一个参数合法，我们还需要更新最大值
					if(maxfd < _fdarr[i]) maxfd = _fdarr[i];

				}

				// 数组和位图表就绪，执行select,时间参数不做处理，进行非阻塞式等待。
				int ret = select(maxfd + 1,&rfds,nullptr,nullptr,nullptr);
				// 检查返回值
				if(ret > 0)
				{
					// 有文件描述符就绪执行对应的任务,任务就是建立链接，将新的文件描述符给select托管
                    logMessage(NORMAL, "get a new link...");
                    HandlerEvent(rfds);
				}
				else if(ret == 0)
				{
                    logMessage(NORMAL, "timeout...");
				}
				else
				{
                   logMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
				}


			}
		}

		~SelectServer()
		{
		}

	private:
		uint16_t _port;
		int _listensock;
		int* _fdarr;
	};

}